package com.dxy.hms.socketio;

import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
public class SocketIoSessionService implements ISessionService {

    /**
     * session缓存容器
     * key: sessionId, value: session
     */
    private final Map<String, ISession> sessionMap = new ConcurrentHashMap<>();

    /**
     * userId映射集合
     * 为什么不直接使用userId作为key去缓存session？——考虑到一个用户可能有多端登录的情况，比如微信可以同时在手机和PC上登录
     * key: userId, value: Set<sessionId>
     * 注意使用Guava的多值集合HashMultiMap的话会有线程安全问题
     */
    private final Map<String, Set<String>> userSessionMap = new ConcurrentHashMap<>();

    /**
     * 房间Id映射集合
     * 使用在聊天室场景
     * key: roomId, value: Set<sessionId>
     */
    private final Map<String, Set<String>> roomSessionMap = new ConcurrentHashMap<>();

    /**
     * 标签映射集合
     * 可以在建立socket连接给会话打上标签，比如用户组、角色等等各种乱七八糟的标签都可以在这里打上标记
     * 这样就可以根据标签进行推送多个用户
     * key: tag, value: Set<sessionId>
     */
    private final Map<String, Set<String>> tagSessionMap = new ConcurrentHashMap<>();

    // 设置映射关系
    private static void setMapping(Map<String, Set<String>> map, String key, String sessionId){
        map.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()).add(sessionId);
    }

    // 移除映射关系
    private static void removeMapping(Map<String, Set<String>> map, String key, String sessionId){
        map.computeIfPresent(key, (k, sessions) -> {
            sessions.remove(sessionId);
            // 如果 sessions 为空，可以选择返回 null 以从 map 中移除这个 entry
            return sessions.isEmpty() ? null : sessions;
        });
    }

    // 移除映射关系
    private static void removeMapping(Map<String, Set<String>> map, String sessionId){
        for (String key : map.keySet()) {
            removeMapping(map, key, sessionId);
        }
    }

    // 发送消息
    private void sendMessage(Map<String, Set<String>> map, String key, String event, Object payload){
        Set<String> sessionIds = map.get(key);
        if (CollUtil.isNotEmpty(sessionIds)){
            sessionIds.forEach(sessionId -> {sendMessage(sessionId, event, payload);});
        }
    }

    @Override
    public void addSession(ISession session) {
        sessionMap.put(session.getSessionId(), session);
    }

    @Override
    public ISession getSessionById(String sessionId) {
        return sessionMap.get(sessionId);
    }

    @Override
    public void removeSession(String sessionId) {
        sessionMap.remove(sessionId);
        removeMapping(userSessionMap, sessionId);
        removeMapping(roomSessionMap, sessionId);
        removeMapping(tagSessionMap, sessionId);
    }

    @Override
    public void setUserId(String userId, String sessionId) {
        setMapping(userSessionMap, userId, sessionId);
    }

    @Override
    public void setRoomId(String roomId, String sessionId) {
        setMapping(roomSessionMap, roomId, sessionId);
    }

    @Override
    public void setTag(String tag, String sessionId) {
        setMapping(tagSessionMap, tag, sessionId);
    }


    @Override
    public void sendMessageByUserId(String userId, String event, Object payload) {
        sendMessage(userSessionMap, userId, event, payload);
    }

    @Override
    public void sendMessageByRoomId(String roomId, String event, Object payload) {
        sendMessage(roomSessionMap, roomId, event, payload);
    }

    @Override
    public void sendMessageByTag(String tag, String event, Object payload) {
        sendMessage(tagSessionMap, tag, event, payload);
    }

    @Override
    public void sendMessageByTags(Set<String> tags, String event, Object payload) {
        if (CollUtil.isNotEmpty(tags)){
            tags.forEach(tag -> sendMessageByTag(tag, event, payload));
        }
    }
}
